Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix pmm deadlock #345

Open
wants to merge 8 commits into
base: mainline
Choose a base branch
from

Conversation

sparchatus
Copy link
Contributor

An ugly first attempt to allow pmm frame array refill without deadlocking.

Intended to fix #343

@sparchatus sparchatus requested a review from a team as a code owner December 2, 2024 14:04
@sparchatus
Copy link
Contributor Author

Optimally, we would refill the 4K frames while still holding the paging lock. However, this would need a new interface in pmm to allow refill without taking the paging lock again.

@sparchatus
Copy link
Contributor Author

there is still the deadlock issue where paging wants to get a frame but pmm is waiting for paging to map a new array. It looks like some kind of backoff is required.

@sparchatus sparchatus marked this pull request as draft December 2, 2024 15:01
@sparchatus
Copy link
Contributor Author

based on #344

Also fixes a (potential?) bug where total_free_frames did not count the early array frames.

@sparchatus sparchatus marked this pull request as ready for review December 3, 2024 09:44
@sparchatus sparchatus changed the title WIP: Fix pmm deadlock Fix pmm deadlock Dec 3, 2024
@sparchatus sparchatus force-pushed the fix_pmm_deadlock branch 2 times, most recently from 58ae195 to 6521e20 Compare December 4, 2024 09:33
jcjgraf
jcjgraf previously approved these changes Dec 4, 2024
Copy link
Contributor

@jcjgraf jcjgraf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am no longer able to reproduce the deadlock 👍

@wipawel wipawel self-requested a review December 4, 2024 09:51
mm/pmm.c Outdated Show resolved Hide resolved
spin_lock(&priority_lock);

/* if a refill is already active then they are responsible */
if (!refilling) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe with refilling variable made atomic, this check can be done before taking the lock?

Copy link
Contributor Author

@sparchatus sparchatus Dec 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could do it before and after taking the lock but I don't think it is sufficient to only check before.

Core 0: pmm_lock()             -> ...              -> refilling = true; unlock(priority) -> ...
Core 1: refill_from_paging()   -> if(!refilling)   -> ...                                -> lock(priority) 

Here paging would think there is no refilling but there is.

array = vmap_kern_4k(mfn_to_virt_map(frame->mfn), frame->mfn, L1_PROT);
/* switch to special refilling mode to avoid deadlock with paging */
ASSERT(!refilling);
refilling = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it need to be handled under the lock below?


/* switch back to normal mode */
ASSERT(refilling);
refilling = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same question as above

mm/pmm.c Show resolved Hide resolved
@@ -117,13 +149,19 @@ static frames_array_t *new_frames_array(void) {

init_frames_array(array);

total_free_frames += array->meta.free_count;
/* since paging will back off from refilling we are responsible to do it here */
try_create_4k_frames();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm... why do we need to split frames during a frame array page mapping?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this approach, if paging detects that refilling is already active, it will not recreate the minimum 4k frames itself but delegate it to whoever is already refilling. This avoids double refilling of array frames and also avoids inconsistencies due to two interleaved array refills.

return array;
error:
panic("PMM: Unable to allocate new page for frame array");
UNREACHABLE();
}

static frames_array_t *new_frames_array(void) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could be inline


static void try_create_4k_frames(void);

static void spin_lock_standard() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would rename it to pmm_lock() or something like that

Comment on lines +424 to +443
void *vmap_frame_refill(void *va, mfn_t mfn, bool paging_lock) {
unsigned long _va = _ul(va) & PAGE_ORDER_TO_MASK(PAGE_ORDER_4K);

dprintk("%s: va: 0x%p mfn: 0x%lx\n", __func__, va, mfn);

if (paging_lock)
spin_lock(&vmap_lock);
else
ASSERT(vmap_lock);

va = _vmap_4k(&cr3, _ptr(_va), mfn, L1_PROT, false);

if (paging_lock)
spin_unlock(&vmap_lock);
else
ASSERT(vmap_lock);

return va;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: instead of the paging_lock check I would create two versions of this function: one with locking and another with the ASSERT (or maybe a BUG_ON). Despite code duplication, it probably will pay off later when debugging something else.

Comment on lines -111 to +142
array = vmap_kern_4k(mfn_to_virt_map(frame->mfn), frame->mfn, L1_PROT);
/* switch to special refilling mode to avoid deadlock with paging */
ASSERT(!refilling);
refilling = true;

/* only paging will be allowed to take the lock for writing while refilling */
spin_unlock(&priority_lock);
array = vmap_frame_refill(mfn_to_virt_map(frame->mfn), frame->mfn, from_paging);
spin_lock(&priority_lock);

/* switch back to normal mode */
ASSERT(refilling);
refilling = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it might make sense to handle all this code as independent functions (see my comment for the vmap_frame_refill).

static size_t frames_count[MAX_PAGE_ORDER + 1];

static spinlock_t lock = SPINLOCK_INIT;

static bool refilling;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be atomic variable, maybe?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BUG] PMM Deadlock
3 participants